﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CommonTools.UserControls.FlowChart
{
    

    /// <summary>
    /// FlowBoard.xaml 的交互逻辑
    /// </summary>
    public partial class FlowBoard : UserControl
    {


        public FlowBoardStatus Status
        {
            get { return (FlowBoardStatus)GetValue(StatusProperty); }
            set { SetValue(StatusProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Status.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StatusProperty =
            DependencyProperty.Register("Status", typeof(FlowBoardStatus), typeof(FlowBoard), new PropertyMetadata(FlowBoardStatus.Link));




        public ObservableCollection<LinkLineModel> LinkLineModelList
        {
            get { return (ObservableCollection<LinkLineModel>)GetValue(LinkLineModelListProperty); }
            set { SetValue(LinkLineModelListProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LinkLineModelList.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LinkLineModelListProperty =
            DependencyProperty.Register("LinkLineModelList", typeof(ObservableCollection<LinkLineModel>), typeof(FlowBoard), new PropertyMetadata(null, (sender, e)=>
            {
                var dp = sender as FlowBoard;
                if (dp == null) return;
                dp.GetLinkLineModelList();
            }));



        public ObservableCollection<FlowBaseControl> ControlList
        {
            get { return (ObservableCollection<FlowBaseControl>)GetValue(ControlListProperty); }
            set { SetValue(ControlListProperty, value); }
        }

        // Using a DependencyProperty as the backing store for linkLineControlList.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ControlListProperty =
            DependencyProperty.Register("ControlList", typeof(ObservableCollection<FlowBaseControl>), typeof(FlowBoard), new PropertyMetadata(null));



        public LinkLineControl SelectedLinkLine
        {
            get { return (LinkLineControl)GetValue(SelectedLinkLineProperty); }
            set { SetValue(SelectedLinkLineProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedLinkLine.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedLinkLineProperty =
            DependencyProperty.Register("SelectedLinkLine", typeof(LinkLineControl), typeof(FlowBoard), new PropertyMetadata(null));



        public FlowItemControl SelectedFlowItem
        {
            get { return (FlowItemControl)GetValue(SelectedFlowItemProperty); }
            set { SetValue(SelectedFlowItemProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedFlowItem.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedFlowItemProperty =
            DependencyProperty.Register("SelectedFlowItem", typeof(FlowItemControl), typeof(FlowBoard), new PropertyMetadata(null));



        public ObservableCollection<FlowItemTemplate> FlowItemTemplateList
        {
            get { return (ObservableCollection<FlowItemTemplate>)GetValue(FlowItemTemplateListProperty); }
            set { SetValue(FlowItemTemplateListProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FlowItemTemplateList.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FlowItemTemplateListProperty =
            DependencyProperty.Register("FlowItemTemplateList", typeof(ObservableCollection<FlowItemTemplate>), typeof(FlowBoard), new PropertyMetadata(null));



        public double BoardWidth
        {
            get { return (double)GetValue(BoardWidthProperty); }
            set { SetValue(BoardWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BoardWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BoardWidthProperty =
            DependencyProperty.Register("BoardWidth", typeof(double), typeof(FlowBoard), new PropertyMetadata(2000.0));



        public double BoardHeight
        {
            get { return (double)GetValue(BoardHeightProperty); }
            set { SetValue(BoardHeightProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BoardHeight.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BoardHeightProperty =
            DependencyProperty.Register("BoardHeight", typeof(double), typeof(FlowBoard), new PropertyMetadata(2000.0));



        public int LineAccuracy
        {
            get { return (int)GetValue(LineAccuracyProperty); }
            set { SetValue(LineAccuracyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineAccuracy.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineAccuracyProperty =
            DependencyProperty.Register("LineAccuracy", typeof(int), typeof(FlowBoard), new PropertyMetadata(2));




        public int FlowAccuracy
        {
            get { return (int)GetValue(FlowAccuracyProperty); }
            set { SetValue(FlowAccuracyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FlowAccuracy.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FlowAccuracyProperty =
            DependencyProperty.Register("FlowAccuracy", typeof(int), typeof(FlowBoard), new PropertyMetadata(5));



        public int LineThumbSize
        {
            get { return (int)GetValue(LineThumbSizeProperty); }
            set { SetValue(LineThumbSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineThumbSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineThumbSizeProperty =
            DependencyProperty.Register("LineThumbSize", typeof(int), typeof(FlowBoard), new PropertyMetadata(6));


        public double CanvasX
        {
            get { return (double)GetValue(CanvasXProperty); }
            set { SetValue(CanvasXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CanvasX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CanvasXProperty =
            DependencyProperty.Register("CanvasX", typeof(double), typeof(FlowBoard), new PropertyMetadata(0.0));



        public double CanvasY
        {
            get { return (double)GetValue(CanvasYProperty); }
            set { SetValue(CanvasYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CanvasY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CanvasYProperty =
            DependencyProperty.Register("CanvasY", typeof(double), typeof(FlowBoard), new PropertyMetadata(0.0));



        public double ScaleValue
        {
            get { return (double)GetValue(ScaleValueProperty); }
            set { SetValue(ScaleValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ScaleValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ScaleValueProperty =
            DependencyProperty.Register("ScaleValue", typeof(double), typeof(FlowBoard), new PropertyMetadata(1.0));

        /// <summary>
        /// 上次鼠标左键点击位置
        /// </summary>
        Point lastMouseLeftDownPoint;

        /// <summary>
        /// 鼠标滚轮倍率
        /// </summary>
        double mouseWheelstep = 1.2;

        /// <summary>
        /// 缩放动画
        /// </summary>
        Storyboard scaleStoryBoard;

        /// <summary>
        /// 实际计算缩放
        /// </summary>
        double calc_ScaleValue = 1.0;
        /// <summary>
        /// 实际计算画布X
        /// </summary>
        double calc_CanvasX = 0.0;
        /// <summary>
        /// 实际计算画布Y
        /// </summary>
        double calc_CanvasY = 0.0;
        /// <summary>
        /// 当前编辑的流程项
        /// </summary>
        FlowItemControl currentEditFlowItem = null;

        public FlowBoard()
        {
            InitializeComponent();
            ControlList = new ObservableCollection<FlowBaseControl>();
            InitFlowItemBoardTemplate();
        }

        



        #region 控件事件
        private void Template_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var template = sender as FlowItemTemplate;
            if (template == null) return;
            Status = FlowBoardStatus.Browse;
            currentEditFlowItem = CreateFlowItem(template.Type);

        }



        private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            lastMouseLeftDownPoint = e.GetPosition(sender as Canvas);
            if (!ClickHand(lastMouseLeftDownPoint))
            {
                switch (Status)
                {
                    case FlowBoardStatus.Link:
                        MouseLeftButtonDown_Link(lastMouseLeftDownPoint);
                        break;
                }
            }
        }



        private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            switch (Status)
            {
                case FlowBoardStatus.Link:
                    MouseLeftButtonUp_Link();
                    break;
            }
        }

        private void MouseLeftButtonUp_Link()
        {
            SelectedLinkLine = null;
            currentEditFlowItem = null;
        }

        private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            switch (Status)
            {
                case FlowBoardStatus.Link:
                    break;

            }
        }

        private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            var mousePoint = e.GetPosition(sender as Canvas);
            ScaleHand(mousePoint, e.Delta);
        }



        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            var newPoint = e.GetPosition(sender as Canvas);


            if (e.LeftButton != MouseButtonState.Pressed) return;
            switch (Status)
            {
                case FlowBoardStatus.Browse:
                    if (currentEditFlowItem != null && Mouse.LeftButton == MouseButtonState.Pressed)
                        MouseMove_Editor(newPoint);
                    else
                        MouseMove_Browse(newPoint);
                    break;
                case FlowBoardStatus.Link:
                    MouseMove_Link(newPoint);
                    break;
            }

        }

        private void Control_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {

        }

        private void Control_MouseLeave(object sender, MouseEventArgs e)
        {
            var control = sender as FlowItemControl;
            control.LinkPreview = false;
        }

        private void Control_MouseEnter(object sender, MouseEventArgs e)
        {
            if (Status != FlowBoardStatus.Link || Mouse.LeftButton != MouseButtonState.Pressed) return;
            var control = sender as FlowItemControl;
            control.LinkPreview = true;
        }


        #endregion

        #region 公共
        private FlowItemControl CreateFlowItem(FlowItemTypeEnum type)
        {
            FlowItemControl control = new FlowItemControl()
            {
                Info = new FlowItemModel() { Type = type, Name = GetNewItemName(type) }
            };

            control.MouseEnter += Control_MouseEnter;
            control.MouseLeave += Control_MouseLeave;
            control.MouseDoubleClick += Control_MouseDoubleClick;
            return control;
        }

        

        private string GetNewItemName(FlowItemTypeEnum type)
        {
            switch (type)
            {
                case FlowItemTypeEnum.流程:
                    List<string> names = new List<string>();
                    foreach (var item in ControlList)
                    {
                        var control = item as FlowItemControl;
                        if (control != null) names.Add(control.Info.Name);
                    }
                    int index = 0;
                    while(true)
                    {
                        string newName = $"{type}{index}";
                        if (!names.Contains(newName))
                            return newName;
                        index++;
                    }
                default:
                    return type.ToString();
            }
        }

        private void InitFlowItemBoardTemplate()
        {
            FlowItemTemplateList = new ObservableCollection<FlowItemTemplate>();
            var list = Enum.GetNames(typeof(FlowItemTypeEnum));
            foreach (var item in list)
            {
                FlowItemTemplateList.Add(CreateFlowItemTemplate(((FlowItemTypeEnum)Enum.Parse(typeof(FlowItemTypeEnum), item))));
            }
        }

        private FlowItemTemplate CreateFlowItemTemplate(FlowItemTypeEnum type)
        {
            var template = new FlowItemTemplate()
            {
                Type = type,
                Height = 30,
                Width = 90,
                Margin = new Thickness(5)
            };
            template.MouseLeftButtonDown += Template_MouseLeftButtonDown;
            return template;
        }

        private bool ClickHand(Point point)
        {
            if (ControlList == null) return false;
            List<FlowBaseControl> clickElementList = new List<FlowBaseControl>();
            clickElementList.AddRange(ControlList.Where(t => IntersectsHand(point, t)));
            if (clickElementList.Count == 0) return false;
            var selectedItem = clickElementList.FirstOrDefault(t => t.IsSelected);
            FlowBaseControl temp;
            if (selectedItem == null)
            {
                temp = clickElementList.FirstOrDefault();
            }
            else
            {
                int index = clickElementList.IndexOf(selectedItem);
                temp = clickElementList[(index + 1) % clickElementList.Count];
            }
            if (temp != null)
            {
                ResetSelected();
                temp.IsSelected = true;
                return true;
            }
            return false;
        }

        private bool IntersectsHand(Point point, FlowBaseControl control)
        {
            Rect src = new Rect(point.X - LineAccuracy / 2, point.Y - LineAccuracy / 2, LineAccuracy, LineAccuracy);
            switch (control.GetType().ToString().Split('.').LastOrDefault())
            {
                case "LinkLineControl":
                    var linkline = control as LinkLineControl;
                    if (linkline != null) return IntersectsByLine(src, linkline);
                    else return false;
                case "FlowItemControl":
                    var flowItem = control as FlowItemControl;
                    if (flowItem != null) return IntersectsByItem(src, flowItem);
                    else return false;
                default:
                    return false;
            }

        }

        private bool IntersectsByItem(Rect src, FlowItemControl control)
        {
            if (control.Info == null) return false;
            Rect des = new Rect(control.Info.X, control.Info.Y, control.Width, control.Height);
            return src.IntersectsWith(des);
        }



        private bool IntersectsByLine(Rect src, LinkLineControl control)
        {
            for (int i = 0; i < control.Info.PointList.Count - 1; i++)
            {
                List<Point_Link> points = new List<Point_Link>() { control.Info.PointList[i], control.Info.PointList[i + 1] };
                double minX = points.Min(x => x.X);
                double minY = points.Min(x => x.Y);
                double maxX = points.Max(x => x.X);
                double maxY = points.Max(x => x.Y);
                Rect des = new Rect(minX - LineAccuracy / 2, minY - LineAccuracy / 2, maxX - minX + LineAccuracy, maxY - minY + LineAccuracy);
                if (src.IntersectsWith(des)) return true;
            }
            return false;
        }

        private void ResetSelected()
        {
            foreach (var item in ControlList)
            {
                item.IsSelected = false;
            }
        }

        private void GetLinkLineModelList()
        {
            if (LinkLineModelList == null) return;
            if (ControlList == null) ControlList = new ObservableCollection<FlowBaseControl>();
            else ControlList.Clear();
            foreach (var item in LinkLineModelList)
            {
                ControlList.Add(CreateLinkLineControl(item));
            }
        }

        private LinkLineControl CreateLinkLineControl(LinkLineModel item)
        {
            LinkLineControl control = new LinkLineControl() { Info = item };
            return control;
        }

        private void MouseLeftButtonDown_Link(Point point)
        {
            LinkLineModel info = new LinkLineModel()
            {
                PointList = new ObservableCollection<Point_Link>()
                {
                    new Point_Link((int)((point.X - CanvasX) / ScaleValue) / LineAccuracy * LineAccuracy, 
                    (int)((point.Y - CanvasY) / ScaleValue) / LineAccuracy * LineAccuracy)
                }
            };
            if (ControlList == null) ControlList = new ObservableCollection<FlowBaseControl>();
            ResetSelected();
            SelectedLinkLine = new LinkLineControl() { Info = info, IsSelected = true};
        }

        private void MouseMove_Browse(Point newPoint)
        {
            var px = newPoint.X - lastMouseLeftDownPoint.X;
            var py = newPoint.Y - lastMouseLeftDownPoint.Y;
            calc_CanvasX = CanvasX += px;
            calc_CanvasY = CanvasY += py;
            lastMouseLeftDownPoint = newPoint;
            //Canvas.SetLeft(Rect_Mapping, -calc_CanvasX / calc_ScaleX * MappingWidth / ImageWidth);
            //Canvas.SetTop(Rect_Mapping, -calc_CanvasY / calc_ScaleY * MappingHeight / ImageHeight);
        }

        private void MouseMove_Editor(Point newPoint)
        {
            int x = (int)((newPoint.X - CanvasX) / ScaleValue - currentEditFlowItem.Info.Width / 2) / FlowAccuracy * FlowAccuracy;
            int y = (int)((newPoint.Y - CanvasY) / ScaleValue - currentEditFlowItem.Info.Height / 2) / FlowAccuracy * FlowAccuracy;
            if (ControlList == null) ControlList= new ObservableCollection<FlowBaseControl>();
            if (!ControlList.Contains(currentEditFlowItem)) ControlList.Add(currentEditFlowItem);
            currentEditFlowItem.Info.X = x;
            currentEditFlowItem.Info.Y = y;
        }

        private void MouseMove_Link(Point newPoint)
        {
            if (SelectedLinkLine == null) return;
            if (ControlList == null) ControlList = new ObservableCollection<FlowBaseControl>();
            Point realPoint = new Point(((int)(newPoint.X - CanvasX) / ScaleValue) / LineAccuracy * LineAccuracy - LineAccuracy, 
                ((int)(newPoint.Y - CanvasY) / ScaleValue) / LineAccuracy * LineAccuracy - LineAccuracy);
            SelectedLinkLine.GetLastPoint(new Point(realPoint.X, realPoint.Y));
            if (!ControlList.Contains(SelectedLinkLine))
            {
                ControlList.Add(SelectedLinkLine);
            }
        }

        private void ScaleHand(Point mousePoint, int delta)
        {
            double scale = 1.0, cX = 0.0, cY = 0.0;
            if (delta > 0)
            {
                if (calc_ScaleValue * mouseWheelstep <= 5)
                {
                    double px = mousePoint.X - calc_CanvasX;
                    double py = mousePoint.Y - calc_CanvasY;
                    double px_add = px * (mouseWheelstep - 1.0);
                    double py_add = py * (mouseWheelstep - 1.0);
                    scale = calc_ScaleValue * mouseWheelstep;
                    cX = calc_CanvasX - px_add;
                    cY = calc_CanvasY - py_add;

                    if (scaleStoryBoard == null)
                    {
                        scaleStoryBoard = new Storyboard();
                        scaleStoryBoard.Completed += ScaleStoryBoard_Completed;
                    }
                    else
                    {
                        scaleStoryBoard.Stop();
                        ScaleValue = calc_ScaleValue;
                        scaleStoryBoard.Children.Clear();
                    }
                    calc_ScaleValue = scale;
                    calc_CanvasX = cX;
                    calc_CanvasY = cY;
                    List<DoubleAnimation> animations = CreateAnimationByScale();
                    foreach (var item in animations)
                    {
                        scaleStoryBoard.Children.Add(item);

                    }
                    scaleStoryBoard.Begin(this, true);

                    //Canvas.SetLeft(Rect_Mapping, -cX / sX * MappingWidth / ImageWidth);
                    //Canvas.SetTop(Rect_Mapping, -cY / sY * MappingHeight / ImageHeight);
                    //Rect_Mapping.Width = MappingWidth * showAreaActualWidth / sX / ImageWidth;
                    //Rect_Mapping.Height = MappingHeight * showAreaActualHeight / sY / ImageHeight;
                    //if (Rect_Mapping.Width > MappingWidth || Rect_Mapping.Height > MappingHeight)
                    //    MappingVisit = Visibility.Collapsed;
                    //else
                    //    MappingVisit = Visibility.Visible;
                }
                else
                    return;
            }
            else
            {
                if (calc_ScaleValue / mouseWheelstep >= 0.2)
                {
                    double px = mousePoint.X - calc_CanvasX;
                    double py = mousePoint.Y - calc_CanvasY;
                    double px_add = px * (1.0 - 1.0 / mouseWheelstep);
                    double py_add = py * (1.0 - 1.0 / mouseWheelstep);
                    scale = calc_ScaleValue / mouseWheelstep;
                    cX = calc_CanvasX + px_add;
                    cY = calc_CanvasY + py_add;

                    if (scaleStoryBoard == null)
                    {
                        scaleStoryBoard = new Storyboard();
                        scaleStoryBoard.Completed += ScaleStoryBoard_Completed;
                    }
                    else
                    {
                        scaleStoryBoard.Stop();
                        ScaleValue = calc_ScaleValue;
                        scaleStoryBoard.Children.Clear();
                    }
                    calc_ScaleValue = scale;
                    calc_CanvasX = cX;
                    calc_CanvasY = cY;
                    List<DoubleAnimation> animations = CreateAnimationByScale();
                    foreach (var item in animations)
                    {
                        scaleStoryBoard.Children.Add(item);

                    }
                    scaleStoryBoard.Begin(this, true);


                    //Canvas.SetLeft(Rect_Mapping, -cX / sX * MappingWidth / ImageWidth);
                    //Canvas.SetTop(Rect_Mapping, -cY / sY * MappingHeight / ImageHeight);
                    //Rect_Mapping.Width = MappingWidth * showAreaActualWidth / sX / ImageWidth;
                    //Rect_Mapping.Height = MappingHeight * showAreaActualHeight / sY / ImageHeight;
                    //if (Rect_Mapping.Width > MappingWidth || Rect_Mapping.Height > MappingHeight)
                    //    MappingVisit = Visibility.Collapsed;
                    //else
                    //    MappingVisit = Visibility.Visible;

                    
                }
                else
                    return;
            }
        }

        private List<DoubleAnimation> CreateAnimationByScale()
        {
            List<DoubleAnimation> animations = new List<DoubleAnimation>();
            DoubleAnimation scaleAniamtion = new DoubleAnimation()
            {
                From = ScaleValue,
                To = calc_ScaleValue,
                Duration = new Duration(new TimeSpan(0, 0, 0, 0, 300)),
                AccelerationRatio = 0.3,
                FillBehavior = FillBehavior.Stop,
                AutoReverse = false
            };
            Storyboard.SetTarget(scaleAniamtion, this);
            Storyboard.SetTargetProperty(scaleAniamtion, new PropertyPath("ScaleValue"));
            animations.Add(scaleAniamtion);
            DoubleAnimation canvasXAniamtion = new DoubleAnimation()
            {
                From = CanvasX,
                To = calc_CanvasX,
                Duration = new Duration(new TimeSpan(0, 0, 0, 0, 300)),
                AccelerationRatio = 0.3,
                FillBehavior = FillBehavior.Stop,
                AutoReverse = false
            };
            Storyboard.SetTarget(canvasXAniamtion, this);
            Storyboard.SetTargetProperty(canvasXAniamtion, new PropertyPath("CanvasX"));
            animations.Add(canvasXAniamtion);
            DoubleAnimation canvasYAniamtion = new DoubleAnimation()
            {
                From = CanvasY,
                To = calc_CanvasY,
                Duration = new Duration(new TimeSpan(0, 0, 0, 0, 300)),
                AccelerationRatio = 0.3,
                FillBehavior = FillBehavior.Stop,
                AutoReverse = false
            };
            Storyboard.SetTarget(canvasYAniamtion, this);
            Storyboard.SetTargetProperty(canvasYAniamtion, new PropertyPath("CanvasY"));
            animations.Add(canvasYAniamtion);
            return animations;
        }

        private void ScaleStoryBoard_Completed(object sender, EventArgs e)
        {
            CanvasX = calc_CanvasX;
            CanvasY = calc_CanvasY;
            ScaleValue = calc_ScaleValue;
            scaleStoryBoard.Children.Clear();
        }
        #endregion



    }
}
